package com.zzw.junit5demo;

import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.aggregator.AggregateWith;
import org.junit.jupiter.params.aggregator.ArgumentsAccessor;
import org.junit.jupiter.params.aggregator.ArgumentsAggregator;
import org.junit.jupiter.params.converter.ConvertWith;
import org.junit.jupiter.params.converter.JavaTimeConversionPattern;
import org.junit.jupiter.params.converter.SimpleArgumentConverter;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.ValueSource;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.CountDownLatch;

import static java.time.Duration.ofMillis;
import static java.time.Duration.ofMinutes;
import static org.junit.jupiter.api.Assertions.*;

/**
 * @author zzw
 * @version 1.0
 * @title AssertionsDemo
 * @description <断言以及参数化>
 * @datetime 2023-07-05 23:26
 */
class AssertionsDemo {

    static class Person {
        private String firstName;
        private String lastName;
        private int age;
        private LocalDate dateOfBirth;

        Person(String firstName, String lastName, int age, LocalDate dateOfBirth) {
            this.firstName = firstName;
            this.lastName = lastName;
            this.age = age;
            this.dateOfBirth = dateOfBirth;
        }


        Person(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }

        public String getFirstName() {
            return firstName;
        }

        public String getLastName() {
            return lastName;
        }

        public int getAge() {
            return age;
        }

        public LocalDate getDateOfBirth() {
            return dateOfBirth;
        }
    }

    private final Person person = new Person("Jane", "Doe");


    @ParameterizedTest
    @EnumSource(ChronoUnit.class)
    void testWithExplicitArgumentConversion(@ConvertWith(ToStringArgConverter.class) String argument) {
        assertNotNull(ChronoUnit.valueOf(argument));
    }

    static class ToStringArgConverter extends SimpleArgumentConverter {

        @Override
        protected Object convert(Object source, Class<?> targetType) {
            assertEquals(String.class, targetType, "Can only convert to String");
            if (source instanceof Enum<?>) {
                return ((Enum<?>) source).name();
            }
            return String.valueOf(source);
        }
    }

    @ParameterizedTest
    @ValueSource(strings = { "2023.05.01", "2023.05.31" })
    void testWithExplicitJavaTimeConverter(@JavaTimeConversionPattern("yyyy.MM.dd") LocalDate argument) {
        assertEquals(2023, argument.getYear());
    }

    @ParameterizedTest
    @CsvSource({
            "Jane, Doe, 19, 1990-05-20",
            "John, Doe, 20, 1990-10-22"
    })
    void testWithArgumentsAccessor(ArgumentsAccessor arguments) {
        Person person = new Person(
                arguments.getString(0),
                arguments.getString(1),
                arguments.getInteger(2),
                arguments.get(3, LocalDate.class));

        if (person.getFirstName().equals("Jane")) {
            assertEquals(19, person.getAge());
        } else {
            assertEquals(20, person.getAge());
        }
        assertEquals("Doe", person.getLastName());
        assertEquals(1990, person.getDateOfBirth().getYear());
    }

    @ParameterizedTest(name = "{index} ==> first=''{0}'',last={1},age={2},brith={3}")
    @CsvSource({
            "Jane, Doe, 19, 1990-05-20",
            "John, Doe, 20, 1990-10-22"
    })
    void testWithArgumentsAggregator(@CsvToPerson Person person) {
        if (person.getFirstName().equals("Jane")) {
            assertEquals(19, person.getAge());
        } else {
            assertEquals(20, person.getAge());
        }
        assertEquals("Doe", person.getLastName());
        assertEquals(1990, person.getDateOfBirth().getYear());
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.PARAMETER)
    @AggregateWith(PersonAggregator.class)
    public @interface CsvToPerson {
    }

    static class PersonAggregator implements ArgumentsAggregator {
        @Override
        public Person aggregateArguments(ArgumentsAccessor arguments, ParameterContext context) {
            return new Person(
                    arguments.getString(0),
                    arguments.getString(1),
                    arguments.getInteger(2),
                    arguments.get(3, LocalDate.class));
        }
    }

    @BeforeEach
    void beforeEach(TestInfo testInfo) {
        // ...
    }

    @ParameterizedTest
    @ValueSource(strings = "apple")
    void testWithRegularParameterResolver(String argument, TestReporter testReporter) {
        testReporter.publishEntry("argument", argument);
    }

    @AfterEach
    void afterEach(TestInfo testInfo) {
        // ...
    }
    @Test
    @DisplayName("标准的断言")
    void standardAssertions() {
        assertEquals(2, Math.addExact(1, 1));
        assertEquals(4, Math.multiplyExact(2, 2),
                "The optional failure message is now the last parameter");
        assertTrue('a' < 'b', () -> "断言消息可以延迟评估，避免构建不必要的复杂消息");
    }

    @Test
    @DisplayName("分组断言")
    void groupedAssertions() {
        // 在分组断言中，所有断言都会被执行，所有失败都会一起报告。
        assertAll("person",
                () -> assertEquals("Jane", person.getFirstName()),
                () -> assertEquals("Doe", person.getLastName())
        );
    }

    @Test
    @DisplayName("依赖断言")
    void dependentAssertions() {
        // 在代码块中，如果断言失败，则将跳过同一块中的后续代码。
        assertAll("properties",
                () -> {
                    String firstName = person.getFirstName();
                    assertNotNull(firstName);

                    // 仅在上一个断言成功后执行
                    assertAll("first name",
                            () -> assertTrue(firstName.startsWith("J")),
                            () -> assertTrue(firstName.endsWith("e"))
                    );
                },

                () -> {
                    // 分组断言，独立名字断言的结果进行处理。
                    String lastName = person.getLastName();
                    assertNotNull(lastName);

                    // 仅在上一个断言成功后执行
                    assertAll("last name",
                            () -> assertTrue(lastName.startsWith("D")),
                            () -> assertTrue(lastName.endsWith("e"))
                    );
                }
        );
    }

    @Test
    @DisplayName("测试执行异常")
    void exceptionTesting() {
        Exception exception = assertThrows(ArithmeticException.class, () -> Math.floorMod(1, 0));
        assertEquals("/ by zero", exception.getMessage());
    }

    @Test
    @DisplayName("未超时的")
    void timeoutNotExceeded() {
        assertTimeout(ofMinutes(2), () -> {
            // 执行耗时不到2分钟的任务
        });
    }

    @Test
    @DisplayName("未超时且有返回值的")
    void timeoutNotExceededWithResult() {
        // 以下断言成功，并返回所提供的对象。
        String actualResult = assertTimeout(ofMinutes(2), () -> {
            return "a result";
        });
        assertEquals("a result", actualResult);
    }

    @Test
    @DisplayName("未超时且调用方法返回对象")
    void timeoutNotExceededWithMethod() {
        // 下面的断言调用一个方法引用并返回一个对象。
        String actualGreeting = assertTimeout(ofMinutes(2), AssertionsDemo::greeting);
        assertEquals("Hello, World!", actualGreeting);
    }

    @Test
    @DisplayName("超过超时长")
    void timeoutExceeded() {
        // 以下断言失败，错误消息类似于：执行超出了10毫秒， 超时91毫秒
        assertTimeout(ofMillis(10), () -> {
            // 模拟耗时超过10毫秒的任务
            Thread.sleep(100);
        });
    }

    @Test
    @DisplayName("超过超时优先终止")
    void timeoutExceededWithPreemptiveTermination() {
        // 以下断言失败，错误消息类似于：10毫秒后执行超时
        assertTimeoutPreemptively(ofMillis(10), () -> {
            // 模拟耗时超过10毫秒的任务
            new CountDownLatch(1).await();
        });
    }

    private static String greeting() {
        return "Hello, World!";
    }
}